package cn.omisheep.au.core;

import cn.omisheep.au.annotation.Au;
import cn.omisheep.au.annotation.AuLimit;
import cn.omisheep.au.annotation.AuUser;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author zhou xin chen  😊
 * 联系方式 qq:1269670415  email:xinchenzhou666@gmail.com
 */
@Data
@Slf4j
public class AuCore implements ApplicationContextAware {


    private final AuConfig auConfig;

    private final Map<RequestMethod, Map<String, Set<String>>> auMap = new HashMap<>();
    private final Map<RequestMethod, Map<String, Set<String>>> auExcludeMap = new HashMap<>();
    private final Map<RequestMethod, Map<String, LimitMeta>> auLimitMap = new HashMap<>();


    public AuCore(AuConfig auConfig) {
        this.auConfig = auConfig;
    }


    @Override
    @SneakyThrows
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        AbstractHandlerMethodMapping<RequestMappingInfo> methodMapping =
                (AbstractHandlerMethodMapping<RequestMappingInfo>) applicationContext.getBean("requestMappingHandlerMapping");
        Map<RequestMappingInfo, HandlerMethod> mapRet = methodMapping.getHandlerMethods();

        // init auMap
        // init auExcludeMap
        Map<String, Object> beansWithAu = applicationContext.getBeansWithAnnotation(Au.class);
        initMap(auMap, beansWithAu, mapRet, Au.class.getMethod("value"), auConfig.getAuAccessPermDefault(), 0);
        log.info("init auMap =>\n {}", AuUtil.beautifulJson(auMap));

        initMap(auExcludeMap, beansWithAu, mapRet, Au.class.getMethod("exclude"), auConfig.getForbidPermDefault(), 1);
        log.info("init auExcludeMap =>\n {}", AuUtil.beautifulJson(auExcludeMap));

        // init auLimitMap
        Map<String, Object> beansWithAuLimit = applicationContext.getBeansWithAnnotation(AuLimit.class);
        initAuLimitMap(beansWithAuLimit, mapRet);


    }

    @Data
    @AllArgsConstructor
    public static class LimitMeta {
        private long limit;
        private int limitMaxCount;
        private long relieveTime;
    }

    private void initAuLimitMap(Map<String, Object> beansWithAnnotation,
                                Map<RequestMappingInfo, HandlerMethod> mapRet) {

        for (RequestMethod value : RequestMethod.values()) {
            auLimitMap.put(value, new HashMap<>());
        }

        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : mapRet.entrySet()) {
            Set<RequestMethod> methods = entry.getKey().getMethodsCondition().getMethods();
            Set<String> patternValues = entry.getKey().getPatternValues();
            AuLimit auLimit = entry.getValue().getMethodAnnotation(AuLimit.class); // 方法上的au
            if (auLimit != null) {
                // 获得元信息
                long limit = AuUtil.parseTimeValue(auLimit.limit());
                int limitMaxCount = auLimit.limitMaxCount();
                long relieveTime = AuUtil.parseTimeValue(auLimit.relieveTime());
                LimitMeta limitMeta = new LimitMeta(limit, limitMaxCount, relieveTime);

                // 添加上元信息
                for (RequestMethod method : methods) {
                    for (String patternValue : patternValues) {
                        auLimitMap.get(method).put(auConfig.getPrefix() + patternValue, limitMeta);
                    }
                }
            } else {
                for (RequestMethod method : methods) {
                    for (String patternValue : patternValues) {
                        auLimitMap.get(method).put(auConfig.getPrefix() + patternValue, null);
                    }
                }
            }
        }
        log.info("auLimitMap => \n{}", AuUtil.beautifulJson(auLimitMap));
    }


    @SneakyThrows
    private void initMap(Map<RequestMethod, Map<String, Set<String>>> map,
                         Map<String, Object> beansWithAnnotation,
                         Map<RequestMappingInfo, HandlerMethod> mapRet,
                         Method targetMethod,
                         String defaultPerm, int index) {
        for (RequestMethod value : RequestMethod.values()) {
            map.put(value, new HashMap<>());
        }

        HashMap<String, String[]> aucMap = new HashMap<>();
        // (1) 注入controller上面的au
        for (Map.Entry<String, Object> entry : beansWithAnnotation.entrySet()) {
            Class<?> aClass = AopUtils.getTargetClass(entry.getValue());
            Au au = aClass.getAnnotation(Au.class);

            String[] strings = (String[]) targetMethod.invoke(au);
            if (strings.length == 0) {
                if (defaultPerm != null && !defaultPerm.equals("")) {
                    aucMap.put(aClass.getName(), new String[]{defaultPerm});
                }
            } else {
                aucMap.put(aClass.getName(), strings);
            }
        }

        // (2) 循环所有的方法，也就是所有的path，添加上权限标记
        mLabel:
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : mapRet.entrySet()) {
            Set<RequestMethod> methods = entry.getKey().getMethodsCondition().getMethods();
            Set<String> patternValues = entry.getKey().getPatternValues();
            Au au = entry.getValue().getMethodAnnotation(Au.class); // 方法上的au

            // (3.0) 如果关闭，则加上any，然后跳过
            if (index == 0 && au != null) {
                boolean open = (boolean) Au.class.getMethod("open").invoke(au);
                if (!open) {
                    foreachMethodAndPatternPathPut(methods, patternValues, map, getOneList(auConfig.getAnyPermDefault()));
                    continue;
                }
            }

            // (3.1) 将controller上的权限加到这个方法的所有path上  将所有含有au的Controller 路径都加上controller的au
            String[] publicAu = aucMap.get(entry.getValue().getBeanType().getName());
            if (publicAu != null) {
                foreachMethodAndPatternPathPutOrUpdate(methods, patternValues, map, getOneList(publicAu));
            }

            // (4) 判断该方法的所有参数，如有有@AuUser，且此时这个路径上没有权限，则加上默认的au权限
            boolean flag = false; // 如果为真，表面这个方法上有authority为真的AuUser
            if (index == 0) {
                label:
                for (MethodParameter methodParameter : entry.getValue().getMethodParameters()) {
                    for (Annotation parameterAnnotation : methodParameter.getParameterAnnotations()) {
                        // 扫描所有参数
                        if (parameterAnnotation.annotationType().equals(AuUser.class)) {
                            // 如果参数上有AuUser注解
                            AuUser auUser = (AuUser) parameterAnnotation;
                            if (auUser.authority()) {
                                // 并且注解的authority值为真
                                foreachMethodAndPatternPathPutOrUpdate(methods, patternValues, map, getOneList(auConfig.getAuAccessPermDefault()));
                                flag = true; // 表明该方法有AuUser
                                continue label;
                            } else {
                                foreachMethodAndPatternPathPut(methods, patternValues, map, getOneList(auConfig.getAnyPermDefault()));
                                continue mLabel;
                            }
                        }
                    }
                }
            }
            // 这个方法上有authority为false的AuUser，则其他的所有权限认证失效
            // (5.1) 获得默认au
            Set<String> auList = new HashSet<>();
            if (au == null) {
                if (index == 0 && !flag) {// 如果这个方法上没有 authority为真的AuUser，那么设置为any
                    if (publicAu != null && publicAu.length == 0) {
                        auList = getOneList(auConfig.getAnyPermDefault());
                    }
                    if (publicAu == null) {
                        auList = getOneList(auConfig.getAnyPermDefault());
                    }
                } else if (auConfig.getForbidPermDefault() != null && !auConfig.getForbidPermDefault().equals("")) {
                    auList = getOneList(auConfig.getForbidPermDefault());
                }
            } else {
                String[] strings = (String[]) targetMethod.invoke(au);
                if (strings.length == 0 && defaultPerm != null && !defaultPerm.equals("")) {
                    auList = getOneList(defaultPerm);
                } else {
                    auList = getOneList(strings);
                }
            }

            // (5.2) 为每一个请求方法加上@Au上的权限没有则用默认的au
            foreachMethodAndPatternPathPutOrUpdate(methods, patternValues, map, auList);
        }
    }

    private HashSet<String> getOneList(String[] value) {
        HashSet<String> strings = new HashSet<>();
        for (String s : value) {
            String[] split = s.split(auConfig.getPermSeparator());
            for (String s1 : split) {
                strings.add(s1.trim());
            }
        }
        return strings;
    }

    private HashSet<String> getOneList(String value) {
        String[] split = value.split(auConfig.getPermSeparator());
        return getOneList(split);
    }

    private void foreachMethodAndPatternPathPut(Set<RequestMethod> methods, Set<String> patternValues, Map<RequestMethod, Map<String, Set<String>>> map, Set<String> list) {
        for (RequestMethod method : methods) {
            for (String patternValue : patternValues) {
                map.get(method).put(auConfig.getPrefix() + patternValue, list);
            }
        }
    }

    private void foreachMethodAndPatternPathPutOrUpdate(Set<RequestMethod> methods, Set<String> patternValues, Map<RequestMethod, Map<String, Set<String>>> map, Set<String> list) {
        for (RequestMethod method : methods) {
            for (String patternValue : patternValues) {
                Set<String> strings = map.get(method).get(auConfig.getPrefix() + patternValue);
                if (strings == null) {
                    map.get(method).put(auConfig.getPrefix() + patternValue, list);
                } else {
                    strings.addAll(list);
                }
            }
        }
    }

}
